package ru.stork.vasilisa.model

import ru.stork.vasilisa.exceptions.VasilisaModelException

import ru.stork.vasilisa.lang
import com.wordpress.michid.Memoize._

object Model {

  private def _fromNode(foreingNs: Namespace, t: lang.Model): VasilisaType = {

    lazy val ns: Namespace = VasilisaType.nsReconfigureWithPrivates(foreingNs, t)

    new Model(

      ns = ns,
      node = t,
      name = t.name,

      typeParameters = t.params match {
        case Some(x) => x.toSet
        case None => Set()
      },

      rawSuperType = t.superType match {
        case Some(x) => Some(Declaration.fromNode(ns, x))
        case None => None
      },

      rawProperties = t.declarations
        .collect { case x: lang.Property => x }
        .map (x => Property.fromNode(ns, x))
        .toSet
    )
  }

  val fromNode = memoize(_fromNode _)
}

/**
 * The base working model.
 * @author aleksey.fomkin@gmail.com
 */
class Model(ns: Namespace,
            name: String,
            node: lang.Node,
            typeParameters: Set[String],

            val rawSuperType: Option[Declaration],
            val rawProperties: Set[Property])

  extends VasilisaType(ns, name, node, typeParameters) {

  object ModelDeclaration {
    def apply(x: Declaration) = x.t.get match {
      case _: Variant => throw new VasilisaModelException(node, "Trait can't inherit the variant");
      case t: Model => new ModelDeclaration(t, x.typeParameters, x.node)
    }
  }

  class ModelDeclaration(val t: Model,
                         val typeParameters: Option[List[Type]],
                         val node: lang.Node)

  lazy val validSuperModel: Option[ModelDeclaration] = rawSuperType match {
    case Some(x) => Some(ModelDeclaration(x))
    case None => None
  }

  @throws(classOf[VasilisaModelException])
  lazy val validProperties: Set[Property] = {

    @throws(classOf[VasilisaModelException])
    def findModelConflicts(models: List[Model]): Boolean = models match {
      case ct :: tl =>
        for (t <- tl)
          for (p <- ct.rawProperties) {
            if (t.getPropertyDefined(p.name)) true;
          }
        findModelConflicts(tl)
      case _ => false;
    }

    // Method search property in tarit list
    def findPropertyInModels(decls: Set[ModelDeclaration], p: String): Boolean = {
      for (d <- decls) {
        if (d.t.getPropertyDefined(p))
          true;
      }

      false;
    }

    // Chech type is valid.
    // FIXME move to type?
    def validateType(typ: Type): Boolean = typ match {
      case Parameter(name, _) => typeParameters.contains(name)
      case Declaration(trt, params, _) => params match {
        case Some(params) =>
          def validateTypeList(tl: List[Type]): Boolean = tl match {
            case x :: Nil => validateType(x)
            case x :: xs => validateType(x) && validateTypeList(xs)
            case Nil => true
          }
          validateTypeList(params)
        case None => true
      }
    }

    for (prop <- rawProperties) {
      // Find property in supertype
      validSuperModel match {
        case Some(x) => if (x.t.getPropertyDefined(prop.name))
          throw new VasilisaModelException(x.node, VasilisaModelException.ERR_ALREADY_DEFINED);
        case None =>
      }

      if (!validateType(prop.typ))
        throw new VasilisaModelException(prop.node, VasilisaModelException.ERR_INVALID_TYPE_PARAM);
    }

    rawProperties
  }

  val getPropertyDefined = memoize((x: String) =>
    rawProperties.find(p => p.name == x) match {
      case Some(_) => true
      case None => false
    }
  )

  @throws(classOf[VasilisaModelException])
  def validateName() {
    if (name == null || name == "")
      throw new VasilisaModelException(node, VasilisaModelException.ERR_INVALID_NAME);
  }

  @throws(classOf[VasilisaModelException])
  def validate() {
    validateName();
  }
}